#!/usr/anim/bin/pypix
"""
print p4configs.depots

for d in p4config.depots:
    print p4configs.depot[d]
    print p4configs.depot[d]['p4port']

to use a generated module:

    import myconfig
    print myconfig.keys
    for i in myconfig.keys:
        print myconfig.data
"""

import sys
import re
import optparse
import cx_Oracle
import getpass

def pr(s):
    global pr_output
    pr_output.write(s+'\n')

#-----------------------------------------------------------------------
# Tdat -- container for everything we know about the table
#-----------------------------------------------------------------------
class Tdat:
    """table data: everything we need to know about a table"""
    def dump(self):
        dd=self
        print '   table=%s'%(dd.table)
        print 'singular=%s'%(dd.singular)
        print '      pk=%s'%(dd.pk)
        print '  tabcom=%s'%(dd.tabcom)
        print '    cols=%s'%(dd.cols)
        print '   cols2=%s'%(dd.cols2)
        print '  colcom=%s'%(dd.colcom)
        print '  pkdata=%s'%(dd.pkdata)
        print '   cdata=%s'%(dd.cdata)

#-----------------------------------------------------------------------
# getdata -- populate the Tdat from the database
#-----------------------------------------------------------------------

def getdata(connstr, tblname):
    """get the data out of the database"""

    conn = cx_Oracle.connect(connstr)
    curs = conn.cursor()
    curs.arraysize=50
    dd=Tdat()

    #---------------------------------------
    # 1. table name
    #---------------------------------------
    dd.table=tblname
    dd.singular=tblname.rstrip('s')

    #---------------------------------------
    # 2. primary key
    #---------------------------------------
    curs.execute(
        """select column_name
           from all_cons_columns a
           join all_constraints c  on a.constraint_name = c.constraint_name 
           where c.table_name = :1 and c.constraint_type = 'P'""",
                 [dd.table.upper()])
    dd.pk=curs.fetchone()[0].lower()

    #---------------------------------------
    # 3. column names
    #---------------------------------------
    curs.execute(
        """select column_name from user_tab_columns
           where table_name = :1 order by column_id""",
           [dd.table.upper()])
    dd.cols = [i[0].lower() for i in curs]
    dd.cols2=[i for i in dd.cols]
    dd.cols2.remove(dd.pk)

    #---------------------------------------
    # 4. table comment
    #---------------------------------------
    curs.execute(
        """select comments from user_tab_comments where table_name = :1""",
        [dd.table.upper()])
    dd.tabcom=curs.fetchone()[0]

    #---------------------------------------
    # 5. column comments
    #---------------------------------------
    curs.execute("""select column_name, comments from user_col_comments
                    where table_name = :1""",
                    [dd.table.upper()])
    dd.colcom={}
    for i in curs:
        dd.colcom[i[0].lower()]=i[1]

    #---------------------------------------
    # 6. all rows of data
    #---------------------------------------

    dd.cdata={}
    dd.pkdata=[]
    curs.execute("select %s,%s from %s order by %s"%
                  (dd.pk,','.join(dd.cols2),dd.table,dd.pk))
    for r in curs:
        pk=r[0]
        dd.pkdata.append(pk)
        cols=r[1:]
        dd.cdata[pk]=cols

    conn.close()
    return dd

#-----------------------------------------------------------------------
# gen_python -- dump into python data structure
#-----------------------------------------------------------------------
def gen_python(dd):
    """generate python data"""
    pr('"""')
    pr('%s.py -- baked in parameters'%(dd.table))
    pr('autogenerated by configomat.py')
    pr('')
    pr('    table name  : %s'%(dd.table))
    pr('    primary key : %s'%(dd.pk))
    pr('    description : %s'%(dd.tabcom))
    pr('')
    pr('    columns:')
    for c in dd.cols:
        pr('        %15s - %s'%(c,dd.colcom[c]))
    pr('"""')
    pr('')

    pr("#-------------------------------------")
    pr("# all keys")
    pr("#-------------------------------------")
    # TODO: print table or keys???
    #pr('%s=['%(dd.table))
    pr('keys=[')
    for r in dd.pkdata:
        pr("    '%s',"%(r))
    pr(']')
    pr('')

    # TODO: print singular or data???
    #pr('%s={'%(dd.singular))
    pr('data={')
    for pkd in dd.pkdata:
        pr("    '%s':{"%(pkd))
        for (cname,cdata) in zip(dd.cols2,dd.cdata[pkd]):
            pr("        '%s':'%s',"%(cname,cdata))
        pr("    },")
    pr('}')

#-----------------------------------------------------------------------
# gperf helper functions
#-----------------------------------------------------------------------
COLS=80
def fb1():
    """print a flowerbox horizontal line"""
    pr('/*%s*/'%('-'*(COLS-4)))

def fb(s):
    """flowerbox a C comment"""
    pr('/* %-74s */'%(s))

def cquote(x):
    """enquote a string with c rules"""
    return str(x).replace('\\','\\\\').replace('"','\\"')

#-----------------------------------------------------------------------
# gen_gperf -- dump into gperf data structure
#-----------------------------------------------------------------------
def gen_gperf(dd):
    """generate gperf data"""
    fb1()
    fb('%s.gperf -- baked in parameters'%(dd.table))
    fb('autogenerated by configomat.py')
    fb('')
    fb('  table name  : %s'%(dd.table))
    fb('  primary key : %s'%(dd.pk))
    fb('  description : %s'%(dd.tabcom))
    fb('')
    fb('  columns:')
    for c in dd.cols:
        fb('      %15s - %s'%(c,dd.colcom[c]))
    fb('')
    fb('to compile:')
    fb('  gperf -t --ignore-case --output=foo.c foo.gperf')
    fb('')
    fb('usage: TODO cleanup')
    fb('    char* mytype = mimetype("jpg")')
    fb('')
    fb('notes:')
    fb('    returns NULL if the thing is not found.')
    fb1()

    #---------------------------------------
    pr('%includes')
    pr('%%define lookup-function-name %s_lookup'%(dd.table))
    pr('%%define hash-function-name   %s_hash'%(dd.table))
    pr('struct %s_tbl {'%(dd.table))
    pr('    char *%s; /* PK */'%(dd.pk))
    for c in dd.cols2:
        pr('    char *%s;'%(c))
    pr('}')
    pr('%%')
    for pk in dd.pkdata:
        line='"%s"'%(pk)
        for c in dd.cdata[pk]:
            line+=',"%s"'%(cquote(c))
        pr(line)
    pr('%%')
    pr('')

    #---------------------------------------
    pr('char *%s_keys[] = {'%(dd.table))
    #pr('    "mpg-test",)
    for pk in dd.pkdata:
        pr('    "%s",'%(cquote(pk)))
    pr('};')

    #---------------------------------------
    pr('')
    fb1()
    fb('%s -- primary interface to this module'%(dd.table))
    fb1()
    pr('struct %s_tbl *%s_get(char *key)'%(dd.table,dd.table))
    pr('{')
    pr('    int len = strlen(key);')
    pr('    if (len == 0)')
    pr('        return NULL;')
    pr('    struct %s_tbl *v = %s_lookup(key, len);'%(dd.table,dd.table))
    pr('    return v;')
    pr('}')
    pr('')
    pr('#include <stdio.h>')

    #---------------------------------------
    for c in dd.cols2:
        pr('')
        pr('char *%s_get%s(char *key)'%(dd.table,c))
        pr('{')
        pr('    struct %s_tbl *v = %s_get(key);'%(dd.table,dd.table))
        pr('    return v ? v->%s : NULL;'%(c))
        pr('}')

    #---------------------------------------
    tt=dd.table
    pr('')
    pr('#ifdef TEST')
    pr('#include <stdio.h>')
    pr('int main()')
    pr('{')
    pr('    int i;')
    pr('    char *k;')
    pr('    struct %s_tbl *v;'%(dd.table))
    pr('    for (i = 0; i < sizeof(%s_keys)/sizeof(*%s_keys); ++i) {'%(tt,tt))
    pr('        k = %s_keys[i];'%(tt))
    pr('        printf("%s\\n", k);')
    for c in dd.cols2:
        pr('        printf("    %12s : %%s\\n", %s_get%s(k));'% (c,tt,c))
    pr('        printf("\\n");')
    pr('    }')
    pr('    printf("nomatch case:\\n");')
    pr('    v = %s_get("");'%(dd.table))
    pr('    printf("    %p\\n", v);')
    pr('    printf("match case:\\n");')
    pr('    v = %s_get(%s_keys[0]);'%(dd.table,dd.table))
    pr('    printf("    %p\\n", v);')
    pr('    return 0;')
    pr('}')
    pr('#endif')

#-----------------------------------------------------------------------
# gen_header -- generate C/C++ header file
#-----------------------------------------------------------------------
def gen_header(dd,fname):
    """generate header file"""

    guard  = '_configomat_'+fname.replace('.','_')
    hh=open(fname,"w")
    print >>hh,('/* %s -- C/C++ decls for table %s */'%(fname, dd.table))
    print >>hh,('/* autogenerated by configomat.py */')
    print >>hh,('')
    print >>hh,('#ifndef %s'%(guard))
    print >>hh,('#define %s'%(guard))
    print >>hh,('')

    #---------------------------------------
    print >>hh,('struct %s_tbl {'%(dd.table))
    print >>hh,('    char *%s; /* PK */'%(dd.pk))
    for c in dd.cols2:
        print >>hh,('    char *%s;'%(c))
    print >>hh,('}')
    print >>hh,('')

    #---------------------------------------
    print >>hh,('extern struct %s_tbl *%s_get(char *key);'%(dd.table,dd.table))
    for c in dd.cols2:
        print >>hh,('extern char *%s_get%s(char *key);'%(dd.table,c))
    print >>hh,('')
    print >>hh,('#endif /*  %s */'%(guard))

    hh.close()

#-----------------------------------------------------------------------
# main -- you know what it does
#-----------------------------------------------------------------------
def main():
    p = optparse.OptionParser(usage="usage: %prog options pkg...")
    p.add_option("-C","--conn",action="store",type="string",dest="conn",
                 help="Database connection string")
    p.add_option("-O","--output",action="store",type="string",dest="output",
                 help="output file name")
    p.add_option("","--lang",action="store",type="string",dest="lang",
                 help="language binding",default='python')
    p.add_option("-P","--pass",action="store",type="string",dest="password",
                 help="Database password")
    p.add_option("","--dump",action="store_true",dest="dump",
                 help="dump the parsed table",default=False)
    p.add_option("-t","--table",action="store",type="string",dest="table",
                 help="database table to digest")
    p.add_option("-H","--header",action="store",type="string",dest="header",
                 help="generate C/C++ header file")
    (opts,args) = p.parse_args()

    # password handling, connection string reconstruction
    oraconnre = re.compile("^([^/@]*)(/([^@]*))?(@(.*))?$")
    user,password,host=oraconnre.match(opts.conn).groups()[0::2]
    if opts.password:
        password=opts.password
    if not password:
        password=getpass.getpass()
    connstr="%s/%s"%(user,password)
    if host:
        connstr+="@%s" % host

    # set output
    global pr_output
    if opts.output:
        pr_output=open(opts.output,"w")
    else:
        pr_output=sys.stdout

    dd = getdata(connstr, opts.table)
    if opts.dump==True:
        dd.dump()
    elif opts.lang=='python':
        gen_python(dd)
    elif opts.lang=='gperf':
        gen_gperf(dd)
    else:
        sys.stderr.write("unknown --lang: %s\n"%(opts.lang))
        sys.exit(1)
    if opts.header is not None:
        gen_header(dd,opts.header)

    sys.exit(0)

if __name__=='__main__':
    main()
